package com.jianguozh.difs.framework.aop;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.jianguozh.difs.framework.constant.enums.LogTypeEnum;
import com.jianguozh.difs.framework.event.WriteLogEvent;
import com.jianguozh.difs.framework.model.OperateLog;
import com.jianguozh.difs.framework.utils.SecurityUtils;
import com.jianguozh.difs.framework.utils.SpringUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 操作日志切面
 *
 * @author hanrs
 * @email 4076130@qq.com
 * @since 2022/6/9 17:02
 */
@Aspect
@Slf4j
@Component
public class OperateLogAspect {

    @Around("@annotation(writeLog)")
    @SneakyThrows
    public Object around(ProceedingJoinPoint point, com.jianguozh.difs.framework.annotation.WriteLog writeLog) {
        String strClassName = point.getTarget().getClass().getName();
        String strMethodName = point.getSignature().getName();

        log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);

        String value = writeLog.value();
        String expression = writeLog.expression();

        // 当前表达式存在 SPEL，会覆盖 value 的值
        if (StrUtil.isNotBlank(expression)) {
            // 解析SPEL
            MethodSignature signature = (MethodSignature) point.getSignature();
            EvaluationContext context = getContext(point.getArgs(), signature.getMethod());
            try {
                value = getValue(context, expression, String.class);
            } catch (Exception e) {
                // SPEL 表达式异常，获取 value 的值
                log.error("@SysLog 解析SPEL {} 异常", expression);
            }
        }

        OperateLog operateLog = new OperateLog();
        operateLog.setTitle(value);
        operateLog.setOperator(SecurityUtils.getUsername());
        operateLog.setOperateDate(LocalDateTime.now());
        operateLog.setAppId(SecurityUtils.getLoginUser() != null ? SecurityUtils.getLoginUser().getIssueClientId() : "unknown");

        String params = operateLog.getParams();
        if (StrUtil.isBlank(params)) {
            Object args = point.getArgs();
            if (ObjectUtil.isNotNull(args)) {
                operateLog.setParams(argsArrayToString(point.getArgs()));
            }
        }

        Long startTime = System.currentTimeMillis();
        Object obj;

        try {
            obj = point.proceed();
        } catch (Exception e) {
            operateLog.setType(LogTypeEnum.ERROR);
            operateLog.setException(e.getMessage());
            throw e;
        } finally {
            Long endTime = System.currentTimeMillis();
            operateLog.setCost(endTime - startTime);
            // 发送异步日志事件
            SpringUtils.getApplicationContext().publishEvent(new WriteLogEvent(operateLog));
        }

        return obj;
    }

    /**
     * 获取spel 定义的参数值
     *
     * @param context 参数容器
     * @param key     key
     * @param clazz   需要返回的类型
     * @param <T>     返回泛型
     * @return 参数值
     */
    private <T> T getValue(EvaluationContext context, String key, Class<T> clazz) {
        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        Expression expression = spelExpressionParser.parseExpression(key);
        return expression.getValue(context, clazz);
    }

    /**
     * 获取参数容器
     *
     * @param arguments       方法的参数列表
     * @param signatureMethod 被执行的方法体
     * @return 装载参数的容器
     */
    private EvaluationContext getContext(Object[] arguments, Method signatureMethod) {
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(signatureMethod);
        EvaluationContext context = new StandardEvaluationContext();
        if (parameterNames == null) {
            return context;
        }
        for (int i = 0; i < arguments.length; i++) {
            context.setVariable(parameterNames[i], arguments[i]);
        }
        return context;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (ObjectUtil.isNotNull(o)) {
                    try {
                        Object jsonObj = JSONUtil.toJsonStr(o);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                    }
                }
            }
        }
        return params.trim();
    }

}
